package zdream.control.assets;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import zdream.control.region.ItemSpec;

import static zdream.control.assets.FoeDescription.PainterHooks;

/**
 * @author Zdream
 * @date 2024-01-01
 * @since 0.0.1
 */
public class FoeStorage {
	private final Map<String, FoeDescription> foeDescriptionMap = new HashMap<>();

	public void scan() {
		scan("../desktop/res/foes");
	}

	public FoeDescription findByFoeName(String foeName) {
		return foeDescriptionMap.get(foeName);
	}

	public List<FoeDescription> allDescriptions() {
		return new ArrayList<>(foeDescriptionMap.values());
	}

	/* **********
	 * 文件扫描 *
	 ********** */
	private AtomicBoolean scanLock = new AtomicBoolean();
	private final Map<Path, JsonObject> textureCache = new HashMap<>();
	private final Map<FoeDescription, List<Path>> foeTextureRefs = new HashMap<>();

	public boolean scannerIsBusy() {
		return scanLock.get();
	}

	public void scan(String rootPath) {
		final boolean successfully = scanLock.compareAndSet(false, true);
		if (!successfully) {
			throw new IllegalStateException("另一个文件扫描任务在进行中, 请稍后重试");
		}

		try {
			scanInner(rootPath);
		} finally {
			textureCache.clear();
			foeTextureRefs.clear();
			scanLock.set(false);
		}
	}

	public TextureDescription scanTextureDescription(String pathName) {
		final Path path = Paths.get(pathName);
		try {
			final byte[] bytes = Files.readAllBytes(path);
			final TextureDescription description =
					TextureDescription.parse(new String(bytes, StandardCharsets.UTF_8), path);
			formatPathType(description);
			return description;
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private void scanInner(String rootPath) {
		Path root = Paths.get(rootPath).toAbsolutePath().normalize();
		File rootFile = root.toFile();
		if (!rootFile.exists() || !rootFile.isDirectory()) {
			throw new IllegalArgumentException("无法在目录: " + rootPath + "下扫描资源");
		}
		System.out.println("扫描目录: " + rootFile);

		List<FoeDescription> list = scanFolder(root);
		if (list == null) {
			return;
		}

		// 将 list 中所有绝对路径修改为 linux-type 的相对路径
		loadTextures(list, root);
		formatPathType(list);
		list.forEach(d -> foeDescriptionMap.put(d.name, d));
	}

	private List<FoeDescription> scanFolder(Path root) {
		try {
			return Files.list(root)
					.map(this::scanFile)
					.filter(Objects::nonNull)
					.flatMap(List::stream)
					.collect(Collectors.toList());
		} catch (IOException e) {
			System.err.printf("无法扫描目录 %s: %s%n", root, e.getMessage());
			return null;
		}
	}

	private List<FoeDescription> scanFile(Path path) {
		if (Files.isDirectory(path)) {
			return scanFolder(path);
		}

		if (path.toString().endsWith(".json")) {
			FoeDescription description = scanJsonFile(path);
			return description == null ? null : Collections.singletonList(description);
		}
		return null;
	}

	/**
	 * 这个 path 是绝对路径
	 */
	private FoeDescription scanJsonFile(Path path) {
		String fileContent;
		try {
			fileContent = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
		} catch (IOException e) {
			System.err.printf("无法扫描文件 %s: %s%n", path, e.getMessage());
			return null;
		}

		try {
			JsonObject root = JsonParser.parseString(fileContent).getAsJsonObject();
			if (root.has("image")) {
				textureCache.put(path, root);
				return null;
			}
			return loadFoeDescription(root, path);
		} catch (RuntimeException e) {
			System.err.printf("扫描文件失败 %s: %s%n", path, e.getMessage());
			return null;
		}
	}

	private FoeDescription loadFoeDescription(JsonObject object, Path path) {
		FoeDescription description = new FoeDescription();
		description.name = object.get("name").getAsString();
		description.className = object.get("class").getAsString();

		if (object.has("painter")) {
			JsonObject painterObject = object.getAsJsonObject("painter");
			FoeDescription.Painter painter = description.painter = new FoeDescription.Painter();
			if (painterObject.has("class")) {
				painter.className = painterObject.get("class").getAsString();
			}
			if (painterObject.has("z")) {
				painter.z = painterObject.get("z").getAsInt();
			}
			if (painterObject.has("textures")) {
				JsonElement textures = painterObject.get("textures");
				List<Path> texturePaths;

				if (textures.isJsonPrimitive()) {
					texturePaths = Collections.singletonList(resolveRelatedPath(path, textures.getAsString()));
				} else {
					texturePaths = textures.getAsJsonArray()
							.asList().stream()
							.map(JsonElement::getAsString)
							.map(next -> resolveRelatedPath(path, next))
							.collect(Collectors.toList());
				}
				foeTextureRefs.put(description, texturePaths);
			}
			if (painterObject.has("flashWhenAgeBiggerThan")) {
				painter.flashWhenAgeBiggerThan = painterObject.get("flashWhenAgeBiggerThan").getAsLong();
			}

			painter.hooks = painterObject.has("hooks") ?
					loadFoePainterHooks(painterObject.get("hooks").getAsJsonArray()) :
					new PainterHooks[0];
		}

		if (object.has("param")) {
			description.spec = ItemSpec.ofJson(object.getAsJsonObject("param"));
		}

		return description;
	}

	private PainterHooks[] loadFoePainterHooks(JsonArray array) {
		return array.asList()
				.stream()
				.map(JsonElement::getAsJsonObject)
				.map(this::loadFoePainterHooks)
				.toArray(PainterHooks[]::new);
	}

	private PainterHooks loadFoePainterHooks(JsonObject object) {
		PainterHooks hooks = new PainterHooks();
		hooks.type = object.get("type").getAsString();
		hooks.from = Optional.ofNullable(object.get("from")).map(JsonElement::getAsString).orElse(null);
		hooks.to = object.get("to").getAsString();
		hooks.setState = object.get("setState").getAsString();
		return hooks;
	}

	private void loadTextures(List<FoeDescription> list, Path root) {
		for (FoeDescription description : list) {
			if (description.painter == null) {
				continue;
			}

			final List<Path> paths = foeTextureRefs.get(description);
			if (paths == null || paths.isEmpty()) {
				continue;
			}

			List<TextureDescription> textures = new ArrayList<>(paths.size());
			for (Path texturePath : paths) {
				JsonObject textureObject = this.textureCache.get(texturePath);
				if (textureObject == null) {
					System.err.printf("%s 的纹理数据 %s 缺失%n", description, texturePath);
					continue;
				}

				textures.add(TextureDescription.parse(textureObject, texturePath));
			}
			description.painter.textures = textures.toArray(new TextureDescription[0]);
		}
	}

	/**
	 * first = "res/foes/ashes/hp_capsule_a.json"
	 * next = "../mass/mm_common_mass_hp_a_sheet.json"
	 * return "res/foes/ashes/../mass/mm_common_mass_hp_a_sheet.json" = "res/foes/mass/mm_common_mass_hp_a_sheet.json"
	 */
	private static Path resolveRelatedPath(Path first, String next) {
		return first.getParent().resolve(next).normalize();
	}

	private static void formatPathType(List<FoeDescription> list) {
		for (FoeDescription description : list) {
			if (description.painter == null || description.painter.textures == null) {
				continue;
			}

			for (TextureDescription texture : description.painter.textures) {
				formatPathType(texture);
			}
		}
	}

	private static void formatPathType(TextureDescription description) {
		for (TextureDescription.Sheet sheet : description.sheets) {
			// TODO hard-cord: Gdx do not support Windows-format absolute path type.
			int index = sheet.imagePath.indexOf(File.separatorChar + "res" + File.separatorChar);
			if (index >= 0) {
				sheet.imagePath = sheet.imagePath.substring(index + 1);
			}
			sheet.imagePath = sheet.imagePath.replace('\\', '/');
		}
	}
}
